<script lang="tsx">
import { defineComponent, computed, ref, useTemplateRef, onMounted, unref, watch } from 'vue'
import { ElRow, ElCol, ElForm, ElFormItem } from 'element-plus'
import { get, set } from 'lodash-es'
import { initModel, setComponentProps, setGridProp, setTextPlaceholder } from './helper'
import { componentMap } from './helper/componentMap'
import { useRenderSelect } from './components/useRenderSelect'
import type { PropType } from 'vue'
import { ComponentNameEnum, type FormSchema, type FormSetProps } from './types'

const { renderSelectOptions } = useRenderSelect()

export default defineComponent({
  name: 'DynamicForm',
  props: {
    schema: {
      type: Array as PropType<FormSchema[]>,
      default: () => []
    },
    isCol: {
      type: Boolean,
      default: true
    },
    autoSetPlaceholder: {
      type: Boolean,
      default: true
    }
  },
  setup(props, { emit, expose }) {
    // element form 实例
    const elFormEl = useTemplateRef<InstanceType<typeof ElForm>>('elFormRef')

    const formModel = ref<Record<string, any>>({})

    onMounted(() => {
      emit('register', unref(elFormEl)?.$parent, unref(elFormEl))
    })

    // 对表单赋值
    const setValues = (data: Record<string, any> = {}) => {
      formModel.value = Object.assign(unref(formModel), data)
    }

    const setSchema = (schemaProps: FormSetProps[]) => {
      for (const v of props.schema) {
        for (const item of schemaProps) {
          if (v.field === item.field) {
            set(v, item.path, item.value)
          }
        }
      }
    }
    const getOptions = async (fn: (params?: any) => Promise<any>, item: FormSchema) => {
      const options = await fn()
      setSchema([
        {
          field: item.field,
          path: 'componentProps.options',
          value: options
        }
      ])
    }

    expose({ setValues, setSchema, formModel })

    watch(
      props.schema,
      (schema) => {
        formModel.value = initModel(schema, unref(formModel))
      },
      {
        immediate: true,
        deep: true
      }
    )

    const renderWrap = () => {
      const { isCol } = props
      const content = isCol ? (
        <ElRow gutter={20}>{renderFormItemWrap()}</ElRow>
      ) : (
        renderFormItemWrap()
      )
      return content
    }

    const renderFormItemWrap = () => {
      const { schema, isCol } = props
      return schema
        .filter((v) => !v.remove)
        .map((item) => {
          return isCol ? (
            <ElCol {...setGridProp(item.colProps)}>{renderFormItem(item)}</ElCol>
          ) : (
            renderFormItem(item)
          )
        })
    }

    const renderFormItem = (item: FormSchema) => {
      if (
        item.optionApi &&
        (!item.componentProps?.options || !item.componentProps?.options.length)
      ) {
        // 内部自动调用接口，不影响其它渲染
        getOptions(item.optionApi, item)
      }

      const formItemSlots = {
        default: () => {
          const Com = componentMap[item.component] as ReturnType<typeof defineComponent>
          const componentSlots = (item.componentProps as any)?.slots || {}
          const slotsMap: Record<string, any> = { ...componentSlots }

          // select 组件的 options
          if (item.component === ComponentNameEnum.Select) {
            slotsMap.default = () => renderSelectOptions(item)
          }

          const itemVal = computed({
            get: () => get(formModel.value, item.field),
            set: (val) => {
              set(formModel.value, item.field, val)
            }
          })
          return (
            <Com
              vModel={itemVal.value}
              {...(props.autoSetPlaceholder && setTextPlaceholder(item))}
              {...setComponentProps(item)}
            >
              {{ ...slotsMap }}
            </Com>
          )
        }
      }
      return (
        <ElFormItem label={item.label} {...(item.formItemProps || {})} prop={item.field}>
          {formItemSlots}
        </ElFormItem>
      )
    }

    return () => (
      <ElForm ref="elFormRef" model={formModel}>
        {{
          default: () => renderWrap()
        }}
      </ElForm>
    )
  }
})
</script>
